package three_star_craft.sound;

import java.io.IOException;
import java.io.InputStream;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

import org.grlea.log.SimpleLogger;

public class AudioPlayer implements Runnable
{
	private static final SimpleLogger logger = new SimpleLogger(AudioPlayer.class);
	
	private static AudioPlayer instance;
	/**
	 * DataLine to which sound data shall be written in order to play it
	 */
	private SourceDataLine soundLine = null;
	
	private AudioInputStream audioInputStream = null;
	
	private static int BUFFER_SIZE = 1024 * 64;
	
	private Thread workingThread;
	/**
	 * Control flag for the working thread
	 */
	private boolean threadRunning;
	
	public static AudioPlayer instance()
	{
		if(instance == null) {
			instance = new AudioPlayer();
		}
		return instance;
	}
	
	private AudioPlayer()
	{
		logger.info("AudioPlayer instance created");
	}

	public boolean isPlaying() {
		return soundLine != null && soundLine.isRunning();
	}
	
	public boolean setupLine(InputStream stream)
	{
		logger.debug("setupLine called");
		try
		{
		    AudioFormat format = AudioSystem.getAudioFileFormat(stream).getFormat();
		    
			audioInputStream = AudioSystem.getAudioInputStream(stream);
			// prepare DataLine info for SourceDataLine - that is for sending data
			// to Mixer for playing
			DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
			logger.verboseObject("setupLine - DataLine.Info for provided audio format", info);
			soundLine = (SourceDataLine) AudioSystem.getLine(info);
			
			soundLine.open(format);
			soundLine.start();
			
			// create new thread for processing the sound streaming
			// and return from this method immediately
			workingThread = new Thread(this);
			threadRunning = true;
			workingThread.start();
		} 
		catch (UnsupportedAudioFileException e)
		{
			e.printStackTrace();
			return false;
		}
		catch (IOException e)
		{
			e.printStackTrace();
			return false;
		} 
		catch (LineUnavailableException e)
		{
			e.printStackTrace();
			return false;
		}
		logger.debug("setupLine - success");
		return true;
	}
	
	public void tearDownLine()
	{
		logger.debugObject("tearDownLine - workingThread: ", workingThread);
		
		if(workingThread != null && workingThread.isAlive())
		{
			threadRunning = false;
			try
			{
				// wait at most 100ms for the thread to stop
				workingThread.join(100);
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		if(soundLine != null)
		{
			// clean up the DataLine object
			soundLine.drain();
			soundLine.stop();
	        soundLine.close();
		}
		if(audioInputStream != null)
		{
			try
			{
				audioInputStream.close();
			} catch (IOException e)
			{
				logger.errorException(e);
			}
		}
	}

	public void run()
	{
		int bytesRead = 0;
		byte[] buf = new byte[BUFFER_SIZE];
		
		logger.debug("Started working thread - run");
		
		try	{
			do {
				if(!threadRunning) {
					logger.debug("run - threadRunnig flag set to false, breaking the loop");
					break;
				}
				// read next chunk of data from input stream into buffer
				bytesRead = audioInputStream.read(buf, 0, buf.length);
				logger.ludicrousObject("run - bytesRead="+bytesRead+", avail=", audioInputStream.available());
				if (bytesRead > 0)
				{
					// Writes audio data to the mixer via this source data line.
					soundLine.write(buf, 0, bytesRead);
				}
				Thread.sleep(5);
			}
			while (bytesRead != -1);
		} // try
		catch (IOException e)
		{
			e.printStackTrace();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		finally	{
			if(threadRunning)
			{
				logger.debug("run - calling tearDownLine from finally clause");
				tearDownLine();
			}
		}
	} // run
}